﻿using System;
using System.Reflection;

namespace Charisma
{
    public partial class DbContext
    {
        private static readonly MethodInfo setImplementationMethod = typeof(DbContext)
            .GetMethod("SetImplementation", BindingFlags.NonPublic | BindingFlags.Static);

        private void Initialize()
        {
            dbProvider = GetDbProvider();
            Database = dbProvider.GetOrCreateDatabase(ConnectionSettings);
            var builder = new DbModelBuilder(GetType());
            OnModelCreating(builder);
            DbModel = builder.Build();
            dbProvider.Initialize(DbModel);
            CreateRepositories();
        }

        private void CreateRepositories()
        {
            foreach (var configuration in DbModel.Repositories) {
                // Checking whether repository property is already set (manually)
                var property = configuration.Property;
                var repository = (IRepository)property.GetValue(this, new object[] { });
                var wrapper = repository as Repository;
                if (wrapper == null && repository != null) {
                    continue;
                }
                var implementation = Database.CreateRepository(configuration);
                if (wrapper != null) {
                    setImplementationMethod
                        .MakeGenericMethod(configuration.RootElement.Type)
                        .Invoke(null, new object[] { repository, implementation });
                }
                else {
                    repository = implementation;
                }

                if (!property.PropertyType.IsAssignableFrom(repository.GetType())) {
                    repository = ReflectionService.CreateInstance<IRepository>(
                        typeof(Repository<>).MakeGenericType(configuration.RootElement.Type),
                        this);
                    setImplementationMethod
                        .MakeGenericMethod(configuration.RootElement.Type)
                        .Invoke(null, new object[] { repository, implementation });
                }
                property.SetValue(this, repository, new object[] { });
            }
        }

        private static void SetImplementation<T>(IRepository target, IRepository implementation) where T : class
        {
            var typed = target as Repository<T>;
            if (typed != null) {
                typed.implementation = (IRepository<T>)implementation;
            }
        }
    }
}